package com.small.nacos.client.config.http;


import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.common.http.HttpClientConfig;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.utils.ExceptionUtil;
import com.alibaba.nacos.common.utils.UuidUtils;
import small.common.core.constants.CommonConstants;
import small.common.http.constants.HttpConstants;
import small.common.http.util.ContextPathUtil;
import small.common.core.collection.CollectionUtil;
import small.common.core.util.ConvertUtil;
import small.common.core.util.StringUtil;
import com.small.nacos.api.common.ApiCommonConstants;
import com.small.nacos.client.config.NacosConfigService;
import com.small.nacos.client.config.impl.ServerListManager;
import com.small.nacos.client.naming.net.NamingHttpClientManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @Author zhoujin
 * @Date 2022/11/18 17:38
 */
public class ServerHttpAgent implements HttpAgent {

    private final static Logger LOGGER = LoggerFactory.getLogger(NacosConfigService.class);

    private static final NacosRestTemplate NACOS_RESTTEMPLATE = NamingHttpClientManager.getInstance().getNacosRestTemplate();

    private String encode;

    private int maxRetry = 3;

    final ServerListManager serverListMgr;

    public ServerHttpAgent(Properties properties) {
        this.serverListMgr = new ServerListManager(properties);
        init(properties);
    }

    @Override
    public String getName() {
        return serverListMgr.getName();
    }

    @Override
    public String getEncode() {
        return encode;
    }

    @Override
    public String getTenant() {
        return serverListMgr.getTenant();
    }

    @Override
    public HttpRestResult<String> httpGet(String path, Map<String, String> headers, Map<String, String> paramValues, String encode, long readTimeoutMs) throws Exception {
        //最后执行时间
        final long endTime = System.currentTimeMillis() + readTimeoutMs;
        int index = 0;
        int maxRetry = this.maxRetry;
        List<String> serverList = serverListMgr.getCurrentServerAddr();
        if (CollectionUtil.isEmpty(serverList)) {
            LOGGER.error("no available server");
            throw new ConnectException("no available server");
        }
        HttpClientConfig httpConfig = HttpClientConfig.builder()
                .setReadTimeOutMillis(Long.valueOf(readTimeoutMs).intValue())
                .setConTimeOutMillis(1000).build();
        Header newHeaders = getSpasHeaders(paramValues, encode);
        Query query = Query.newInstance().initParams(paramValues);

        do {
            index = (index + 1) % serverList.size();
            String currentServerAddr = serverList.get(index);

            try {
                HttpRestResult<String> result = NACOS_RESTTEMPLATE
                        .get(getUrl(currentServerAddr, path), httpConfig, newHeaders, query, String.class);
                if (isFail(result)) {
                    LOGGER.error("[NACOS ConnectException] currentServerAddr: {}, httpCode: {}",
                            currentServerAddr, result.getCode());
                } else {
                    return result;
                }

            } catch (ConnectException connectException) {
                LOGGER.error("[NACOS ConnectException httpDelete] currentServerAddr:{}, err : {}",
                        currentServerAddr, ExceptionUtil.getStackTrace(connectException));
            } catch (SocketTimeoutException stoe) {
                LOGGER.error("[NACOS SocketTimeoutException httpDelete] currentServerAddr:{}， err : {}",
                        currentServerAddr, ExceptionUtil.getStackTrace(stoe));
            } catch (Exception ex) {
                LOGGER.error(
                        "[NACOS Exception httpDelete] currentServerAddr: " + serverListMgr.getCurrentServerAddr(),
                        ex);
                throw ex;
            }
            maxRetry--;
            if (maxRetry < 0) {
                throw new ConnectException(
                        "[NACOS HTTP-GET] The maximum number of tolerable server reconnection errors has been reached");
            }

        } while (System.currentTimeMillis() <= endTime);

        throw new ConnectException("no available server");
    }


    @Override
    public HttpRestResult<String> httpPost(String path, Map<String, String> headers, Map<String, String> paramValues,
                                           String encode, long readTimeoutMs) throws Exception {
        //最后执行时间
        final long endTime = System.currentTimeMillis() + readTimeoutMs;
        int index = 0;
        int maxRetry = this.maxRetry;
        List<String> serverList = serverListMgr.getCurrentServerAddr();
        if (CollectionUtil.isEmpty(serverList)) {
            LOGGER.error("no available server");
            throw new ConnectException("no available server");
        }
        HttpClientConfig httpConfig = HttpClientConfig.builder()
                .setReadTimeOutMillis(Long.valueOf(readTimeoutMs).intValue())
                .setConTimeOutMillis(1000).build();
        Header newHeaders = getSpasHeaders(paramValues, encode);
        if (headers != null) {
            newHeaders.addAll(headers);
        }
        do {
            index = (index + 1) % serverList.size();
            String currentServerAddr = serverList.get(index);
            try {
                HttpRestResult<String> result = NACOS_RESTTEMPLATE
                        .postForm(getUrl(currentServerAddr, path), httpConfig, newHeaders, paramValues, String.class);
                if (isFail(result)) {
                    LOGGER.error("[NACOS ConnectException] currentServerAddr: {}, httpCode: {}",
                            currentServerAddr, result.getCode());
                } else {
                    return result;
                }

            } catch (ConnectException connectException) {
                LOGGER.error("[NACOS ConnectException httpDelete] currentServerAddr:{}, err : {}",
                        currentServerAddr, ExceptionUtil.getStackTrace(connectException));
            } catch (SocketTimeoutException stoe) {
                LOGGER.error("[NACOS SocketTimeoutException httpDelete] currentServerAddr:{}， err : {}",
                        currentServerAddr, ExceptionUtil.getStackTrace(stoe));
            } catch (Exception ex) {
                LOGGER.error(
                        "[NACOS Exception httpDelete] currentServerAddr: " + serverListMgr.getCurrentServerAddr(),
                        ex);
                throw ex;
            }
            maxRetry--;
            if (maxRetry < 0) {
                throw new ConnectException(
                        "[NACOS HTTP-GET] The maximum number of tolerable server reconnection errors has been reached");
            }

        } while (System.currentTimeMillis() <= endTime);

        throw new ConnectException("no available server");
    }

    private void init(Properties properties) {
        encode = properties.getProperty(ApiCommonConstants.ENCODE);
        if (StringUtil.isBlank(encode)) {
            encode = CommonConstants.ENCODE;
        }
        maxRetry = ConvertUtil.toInt(properties.getProperty(ApiCommonConstants.MAX_RETRY), ApiCommonConstants.DEFAULT_MAX_RETRY);
    }

    private boolean isFail(HttpRestResult<String> result) {
        return result.getCode() == HttpURLConnection.HTTP_INTERNAL_ERROR
                || result.getCode() == HttpURLConnection.HTTP_BAD_GATEWAY
                || result.getCode() == HttpURLConnection.HTTP_UNAVAILABLE;
    }

    private String getUrl(String serverAddr, String relativePath) {
        return ContextPathUtil.normalizeContextUrl(serverAddr) + ContextPathUtil.normalizeContextPath(ServerListManager.getContentPath()) + relativePath;
    }

    private Header getSpasHeaders(Map<String, String> paramValues, String encode) throws Exception {

        String ts = String.valueOf(System.currentTimeMillis());
        Header header = Header.newInstance();
        header.addParam(HttpConstants.ACCEPT_ENCODING,"gzip");
        header.addParam(HttpConstants.REQUEST_ID, UuidUtils.generateUuid());
        header.addParam(HttpConstants.ACCEPT_CHARSET, encode);
        header.addParam(Constants.CLIENT_REQUEST_TS_HEADER, ts);
        return header;
    }
}
